/*
 * ThermistorTable.cpp
 *
 * Copyright 2012 Pieter Agten
 *
 * This file is part of tltgen.
 *
 * tltgen is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * tltgen is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with tltgen.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * @file   ThermistorTableRow.cpp
 * @author Pieter Agten (pieter.agten@gmail.com)
 * @date   26 dec 2012
 */

#include "ThermistorTable.hpp"

#include <stdexcept>

using boost::lexical_cast;
using boost::signals2::connection;

namespace tltgen {

  ThermistorTableRow::ThermistorTableRow(ThermistorTable *table) :
    thermistorTable(table), temperature(NAN), resistance(NAN), voltage(NAN), adcValue(NAN)
  { }

  bool ThermistorTableRow::hasValue(ThermistorTableRow::Column col) const
  {
    switch (col) {
    case TEMPERATURE:
      return hasTemperature();
    case RESISTANCE:
      return hasResistance();
    case VOLTAGE:
      return hasVoltage();
    case ADCVALUE:
      return hasAdcValue();
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }

  double ThermistorTableRow::getValue(ThermistorTableRow::Column col) const
  {
    switch (col) {
    case TEMPERATURE:
      return getTemperature();
    case RESISTANCE:
      return getResistance();
    case VOLTAGE:
      return getVoltage();
    case ADCVALUE:
      return getAdcValue();
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }


  bool ThermistorTableRow::isValidValue(ThermistorTableRow::Column col, double value) const
  {
    switch (col) {
    case TEMPERATURE:
      return isValidTemperature(value);
    case RESISTANCE:
      return isValidResistance(value);
    case VOLTAGE:
      return isValidVoltage(value);
    case ADCVALUE:
      return isValidAdcValue(value);
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }
  
  void ThermistorTableRow::setValue(ThermistorTableRow::Column col, double value)
  {
    switch (col) {
    case TEMPERATURE:
      return setTemperature(value);
    case RESISTANCE:
      return setResistance(value);
    case VOLTAGE:
      return setVoltage(value);
    case ADCVALUE:
      return setAdcValue(value);
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }

  bool ThermistorTableRow::hasValidValue(ThermistorTableRow::Column col) const
  {
    switch (col) {
    case TEMPERATURE:
      return hasValidTemperature();
    case RESISTANCE:
      return hasValidResistance();
    case VOLTAGE:
      return hasValidVoltage();
    case ADCVALUE:
      return hasValidAdcValue();
    default:
      throw std::invalid_argument("Invalid column specified.");
    }
  }


  // Temperature
  bool ThermistorTableRow::hasTemperature() const
  {
    return ! std::isnan(this->temperature);
  }
  Temperature ThermistorTableRow::getTemperature() const
  {
    if (! hasTemperature()) {
      throw std::invalid_argument("This row does not have a temperature value."); 
    }

    return this->temperature;
  }
  bool ThermistorTableRow::isValidTemperature(Temperature t) const
  {
    return getThermistorTable()->isValidTemperature(t);
  }
  bool ThermistorTableRow::hasValidTemperature() const
  {
    return hasTemperature() && isValidTemperature(getTemperature());
  }
  void ThermistorTableRow::setTemperature(Temperature t)
  {
    if (! isValidTemperature(t)) {
      throw std::invalid_argument("Invalid temperature specified.");
    }

    Temperature oldTemperature = this->temperature;
    this->temperature = t;

    if (this->temperature != oldTemperature) {
      signal(COLUMN_CHANGED, this, TEMPERATURE);
      signal(ROW_CHANGED, this, NB_COLUMNS);
    }
  }
  
 

  // Resistance
  bool ThermistorTableRow::hasResistance() const
  {
    return ! std::isnan(this->resistance);
  }
  Resistance ThermistorTableRow::getResistance() const
  {
    if (! hasResistance()) {
      throw std::invalid_argument("This row does not have a resistance value."); 
    }

    return this->resistance;
  }
  bool ThermistorTableRow::isValidResistance(Resistance r) const
  {
    return getThermistorTable()->isValidResistance(r);
  }
  void ThermistorTableRow::setResistance(Resistance r)
  {
    if (! isValidResistance(r)) {
      throw std::invalid_argument("Invalid resistance specified.");
    }
    
    if (this->resistance != r) {
      this->resistance = r;
    
      const ThermistorTable *table = getThermistorTable();
      if (table->hasCircuit()) {
	this->voltage = table->getCircuit()->getOutputVoltage(r);
    
	if (table->hasAnalogToDigitalConverter()) {
	  this->adcValue = table->getAnalogToDigitalConverter()->getOutputValue(this->voltage);
          signal(COLUMN_CHANGED, this, ADCVALUE);
	}
        signal(COLUMN_CHANGED, this, VOLTAGE);
      }
      signal(COLUMN_CHANGED, this, RESISTANCE);
      signal(ROW_CHANGED, this, NB_COLUMNS);
    }
  }
  bool ThermistorTableRow::hasValidResistance() const
  {
    return hasResistance() && isValidResistance(getResistance());
  }

  // Voltage
  bool ThermistorTableRow::hasVoltage() const
  {
    return ! std::isnan(this->voltage);
  }
  Voltage ThermistorTableRow::getVoltage() const
  {
    if (! hasVoltage()) {
      throw std::invalid_argument("This row does not have a voltage value."); 
    }

    return this->voltage;
  }
  bool ThermistorTableRow::isValidVoltage(Voltage v) const
  {
    return getThermistorTable()->isValidVoltage(v);
  }
  bool ThermistorTableRow::hasValidVoltage() const
  {
    return hasVoltage() && isValidVoltage(getVoltage());
  }
  void ThermistorTableRow::setVoltage(Voltage v)
  {
    if (! isValidVoltage(v)) {
      throw std::invalid_argument("Invalid voltage specified.");
    }

    const ThermistorTable *table = getThermistorTable();
    if (! table->hasCircuit()) {
      throw std::invalid_argument("Cannot set voltage: table has no circuit model specified.");
    }
    
    if (this->voltage != v) {
      this->voltage = v;
      this->resistance = table->getCircuit()->getThermistorResistance(v);
    
      if (table->hasAnalogToDigitalConverter()) {
	this->adcValue = table->getAnalogToDigitalConverter()->getOutputValue(v);
        signal(COLUMN_CHANGED, this, ADCVALUE);
      }

      signal(COLUMN_CHANGED, this, RESISTANCE);
      signal(COLUMN_CHANGED, this, VOLTAGE);
      signal(ROW_CHANGED, this, NB_COLUMNS);
    }
  }


  // ADC
  bool ThermistorTableRow::hasAdcValue() const
  {
    return ! std::isnan(this->adcValue);
  }
  int ThermistorTableRow::getAdcValue() const
  {
    if (! hasAdcValue()) {
      throw std::invalid_argument("This row does not have an ADC value."); 
    }

    return this->adcValue;
  }
  void ThermistorTableRow::setAdcValue(int adc)
  {
    if (! isValidAdcValue(adc)) {
      throw std::invalid_argument("Invalid adc value specified.");
    }

    const ThermistorTable *table = getThermistorTable();
    if (! table->hasCircuit()) {
      throw std::invalid_argument("Cannot set ADC value: table has no circuit specified.");
    }
    if (! table->hasAnalogToDigitalConverter()) {
      throw std::invalid_argument("Cannot set ADC value: table has no ADC specified.");
    }
    
    if (this->adcValue != adc) {
      this->adcValue = adc;
      this->voltage = table->getAnalogToDigitalConverter()->getInputVoltage(adc);
      this->resistance = table->getCircuit()->getThermistorResistance(table->getAnalogToDigitalConverter()->getInputVoltage(adc));
    
      signal(COLUMN_CHANGED, this, ADCVALUE);
      signal(COLUMN_CHANGED, this, VOLTAGE);
      signal(COLUMN_CHANGED, this, RESISTANCE);
      signal(ROW_CHANGED, this, NB_COLUMNS);
    }
  }
  bool ThermistorTableRow::isValidAdcValue(int adc) const
  {
    return getThermistorTable()->isValidAdcValue(adc);
  }
  bool ThermistorTableRow::hasValidAdcValue() const
  {
    return hasAdcValue() && isValidAdcValue(getAdcValue());
  }

  

  void ThermistorTableRow::clearTemperature()
  {
    if (hasTemperature()) {
      this->temperature = NAN;
      signal(COLUMN_CHANGED, this, TEMPERATURE);
      signal(ROW_CHANGED, this, NB_COLUMNS);
    }
  }
  void ThermistorTableRow::clearResistanceVoltageAdc()
  {
    bool hadResistance = hasResistance();
    bool hadVoltage = hasVoltage();
    bool hadAdcValue = hasAdcValue();

    this->resistance = NAN;
    this->voltage = NAN;
    this->adcValue = NAN;
    
    if (hadResistance) signal(COLUMN_CHANGED, this, RESISTANCE);
    if (hadVoltage) signal(COLUMN_CHANGED, this, VOLTAGE);
    if (hadAdcValue) signal(COLUMN_CHANGED, this, ADCVALUE);
    if (hadResistance || hadVoltage || hadAdcValue) signal(ROW_CHANGED, this, NB_COLUMNS);
  }

  void ThermistorTableRow::recalculateVoltageAdc()
  {
    if (hasResistance()) {
      const ThermistorTable *table = getThermistorTable();
      if (table->hasCircuit()) {
	this->voltage = table->getCircuit()->getOutputVoltage(this->resistance);
	if (table->hasAnalogToDigitalConverter()) {
	  this->adcValue = table->getAnalogToDigitalConverter()->getOutputValue(this->voltage);
	} else {
	  this->adcValue = NAN;
	}
      } else {
	this->voltage = NAN;
      }
      signal(COLUMN_CHANGED, this, ADCVALUE);
      signal(COLUMN_CHANGED, this, VOLTAGE);
      signal(ROW_CHANGED, this, NB_COLUMNS);
    }
  }

  bool ThermistorTableRow::isValid() const
  {
    return hasValidTemperature() && hasValidResistance();
  }
  
  
  ThermistorTable *ThermistorTableRow::getThermistorTable() const
  {
    return this->thermistorTable;
  }
  
  connection ThermistorTableRow::connectObserver(const Slot &slot)
  {
    return this->signal.connect(slot);
  }

  void ThermistorTableRow::remove()
  {
    signal(REMOVE, this, NB_COLUMNS);
  }
}

//BOOST_CLASS_EXPORT_IMPLEMENT(tltgen::ThermistorTableRow)


